import { CPU } from "./CPU";
import { PPU } from "./PPU";
import { ROM } from "./ROM";
import { PAPU } from "./PAPU";
import { Controller } from "./Controller";
import { Mappers0 } from "./Mappers/Mappers0";
import { Mappers1 } from "./Mappers/Mappers1";
import { Mappers2 } from "./Mappers/Mappers2";
import { Mappers4 } from "./Mappers/Mappers4";

export interface NesUtilOption {
    swfPath: string;

    preferredFrameRate: number;
    /** Time between updating FPS in ms */
    fpsInterval: number;
    showDisplay: boolean;

    /** need cpu fast */
    emulateSound: boolean;
    /** Sound sample rate in hz */
    sampleRate: number;

    /** 1789772.72727272d */
    CPU_FREQ_NTSC: number;
    CPU_FREQ_PAL: number;

    writeFrame(buffer: number[]): void;
    updateStatus(message: string): void;
    writeAudio(buffer: number[]): void;
    onAudioSample(sampleValueL: number, sampleValueR: number): void;
}

export interface NextFuncInfo {
    next: VoidFunction;
    time?: number;
}

export class NesUtil {
    static VERSION = "<%= version %>";

    static Mappers = {
        0: Mappers0,
        1: Mappers1,
        2: Mappers2,
        4: Mappers4,
    };

    private frameTime: number;

    private isRunning = false;
    private fpsFrameCount = 0;
    private romData: Uint8Array = null;

    private frameInterval: number;
    private lastFpsTime: number;
    private fpsInterval: number;

    public opts: NesUtilOption;

    public cpu: CPU;
    public ppu: PPU;
    public papu: PAPU;
    public rom: ROM;
    public mmap: Mappers0;
    public keyboard: Controller;

    private nexts: NextFuncInfo[] = [];

    constructor (opts: Partial<NesUtilOption>) {
        this.opts = {
            swfPath: 'lib/',

            preferredFrameRate: 30,
            fpsInterval: 500, // Time between updating FPS in ms
            showDisplay: true,

            emulateSound: true, //need cpu fast
            sampleRate: 44100, // Sound sample rate in hz

            CPU_FREQ_NTSC: 1789772, //1789772.72727272d;
            CPU_FREQ_PAL: 1773447,

            writeFrame: () => {},
            updateStatus: () => {},
            writeAudio: () => {},
            onAudioSample: () => {},
        };

        if (typeof opts != 'undefined') {
            for (let key in this.opts) {
                if (typeof opts[key] != 'undefined') {
                    this.opts[key] = opts[key];
                }
            }
        }

        this.frameTime = 1000 / this.opts.preferredFrameRate;

        this.cpu = new CPU(this);
        this.ppu = new PPU(this);
        this.papu = new PAPU(this);
        this.mmap = null; // set in loadRom()
        this.keyboard = new Controller(this);

        this.opts.updateStatus("初始化完成，请选择游戏...");
    }

    // Resets the system
    reset () {
        if (this.mmap !== null) {
            this.mmap.reset();
        }

        this.cpu.reset();
        this.ppu.reset();
        this.papu.reset();
    }

    start () {
        if (this.rom !== null && this.rom.valid) {
            // if (!this.isRunning) {
            //     this.isRunning = true;
            if (requestAnimationFrame) {
                requestAnimationFrame(() => {
                    this.frame();
                    this.start();
                })
            } else {
                this.frameInterval = setTimeout(() => {
                    this.frame();
                    this.start();
                }, this.frameTime);
            }
            // this.resetFps();
            // this.printFps();
            // this.fpsInterval = setInterval(function() {
            //     self.printFps();
            // }, this.opts.fpsInterval);
            // }
        } else {
            this.opts.updateStatus("没有加载到游戏，或者格式不支持！");
        }
    }

    public nexttick(next: () => void, time?: number) {
        this.nexts.push({ next, time });
    }

    private triggerNext() {
        if (this.nexts.length > 0) {
            const nexts = [...this.nexts];
            const save = this.nexts = [];
            for (const info of nexts) {
                const { time } = info;
                if (time && time > 0) {
                    info.time--;
                    save.push(info);
                } else {
                    info.next();
                }
            }
        }
    }

    frame () {
        this.ppu.startFrame();
        let cycles = 0;
        const emulateSound = this.opts.emulateSound;
        const cpu = this.cpu;
        const ppu = this.ppu;
        const papu = this.papu;
        FRAMELOOP: for (;;) {
            // console.log(cpu.cyclesToHalt)
            if (cpu.cyclesToHalt === 0) {
                // Execute a CPU instruction
                cycles = cpu.emulate();
                if (emulateSound) {
                    papu.clockFrameCounter(cycles);
                }
                cycles *= 3;
            } else {
                if (cpu.cyclesToHalt > 8) {
                    cycles = 24;
                    if (emulateSound) {
                        papu.clockFrameCounter(8);
                    }
                    cpu.cyclesToHalt -= 8;
                } else {
                    cycles = cpu.cyclesToHalt * 3;
                    if (emulateSound) {
                        papu.clockFrameCounter(cpu.cyclesToHalt);
                    }
                    cpu.cyclesToHalt = 0;
                }
            }
            for (; cycles > 0; cycles--) {
                if (ppu.curX === ppu.spr0HitX &&
                    ppu.f_spVisibility === 1 &&
                    ppu.scanline - 21 === ppu.spr0HitY) {
                    // Set sprite 0 hit flag:
                    ppu.setStatusFlag(ppu.STATUS_SPRITE0HIT, true);
                }
                if (ppu.requestEndFrame) {
                    ppu.nmiCounter--;
                    if (ppu.nmiCounter === 0) {
                        ppu.requestEndFrame = false;
                        //调用endFrame，最后输出帧数据
                        ppu.startVBlank();
                        break FRAMELOOP;
                    }
                }
                ppu.curX++;
                if (ppu.curX === 341) {
                    ppu.curX = 0;
                    ppu.endScanline();
                }
            }
        }
        this.fpsFrameCount++;
        // this.lastFrameTime = +new Date();
        this.triggerNext();
    }

    printFps () {
        const now = new Date().getTime();
        let str = 'Running';
        if (this.lastFpsTime) {
            str += ': ' + (
                this.fpsFrameCount / ((now - this.lastFpsTime) / 1000)
            ).toFixed(2) + ' FPS';
        }
        this.opts.updateStatus(str);
        this.fpsFrameCount = 0;
        this.lastFpsTime = now;
    }

    stop () {
        clearInterval(this.frameInterval);
        clearInterval(this.fpsInterval);
        this.isRunning = false;
    }

    reloadRom () {
        if (this.romData !== null) {
            this.loadRom(this.romData);
        }
    }

    // Loads a ROM file into the CPU and PPU.
    // The ROM file is validated first.
    loadRom (data: Uint8Array) {
        if (this.isRunning) {
            this.stop();
        }

        this.opts.updateStatus("游戏加载中...");

        // Load ROM file:
        this.rom = new ROM(this);
        this.rom.load(data);

        if (this.rom.valid) {
            this.reset();
            this.mmap = this.rom.createMapper();
            if (!this.mmap) {
                return;
            }
            this.mmap.loadROM();
            this.ppu.setMirroring(this.rom.getMirroringType());
            this.romData = data;

            this.opts.updateStatus("加载成功，开始中...");
        } else {
            this.opts.updateStatus("游戏格式不支持!");
        }
        return this.rom.valid;
    }

    resetFps () {
        this.lastFpsTime = null;
        this.fpsFrameCount = 0;
    }

    setFramerate (rate: number) {
        this.opts.preferredFrameRate = rate;
        this.frameTime = 1000 / rate;
        this.papu.setSampleRate(this.opts.sampleRate, false);
    }
}
